﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Hardanger.ViewModel;
using System.Net;
using Hardanger.Model;
using System.ComponentModel;
using System.Collections.ObjectModel;
using Hardanger.Features.ServerFuzzer.DetectionEngine;

namespace Hardanger.Features.ServerFuzzer
{
    public class ServerFuzzerMasterEngine : INotifyPropertyChanged
    {
        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion //Events

        #region Fields

        private static long _resultCount = 0;
        private static ServerFuzzerMasterEngine _current = null;
        static private ServerFuzzerViewModel CurrentFuzzing = null;
        private static System.Windows.Threading.Dispatcher _mainDispatcher;
        private ObservableCollection<IDetectionEngine> _activeDetectionEngines;
        private ObservableCollection<string> _selectedDetectionEngines;
        private Boolean _isRunning;
        private long _iterations = 0;
        private long _currentSessionResultCount = 0;
        private int _currentNumberOfThreads = 0;
        private int _maxThreads = 0;
        private int _requestTimeOut;

        private bool _stripIfModifiedSince = true;
        private bool _stripPragmaNoCache = true;

        #endregion //Fields

        #region Properties

        public int MaxThreads
        {
            get { return _maxThreads; }
            set
            {
                _maxThreads = value;
            }
        }
        public int RequestPause { get; set; }
        public static long ResultCount
        {
            get
            {
                return _resultCount;
            }
            set
            {
                _resultCount = value;
                Current.NotifyPropertyChanged("ResultCount");
            }
        }
        public long CurrentSessionResultCount
        {
            get { return _currentSessionResultCount; }
            set
            {
                _currentSessionResultCount = value;
                NotifyPropertyChanged("CurrentSessionResultCount");
            }
        }
        public int CurrentNumberOfThreads
        {
            get { return _currentNumberOfThreads; }
            private set
            {
                _currentNumberOfThreads = value;
                NotifyPropertyChanged("CurrentNumberOfThreads");
            }
        }
        public int RequestTimeOut
        {
            get
            {
                return _requestTimeOut;
            }
            set
            {
                _requestTimeOut = value;
                NotifyPropertyChanged("RequestTimeOut");
            }
        }
        public long Iterations
        {
            get
            {
                return _iterations;
            }
            private set
            {
                _iterations = value;
                NotifyPropertyChanged("Iterations");
            }
        }
        static public ServerFuzzerMasterEngine Current
        {
            get
            {
                if (_current == null)
                {
                    _current = new ServerFuzzerMasterEngine();
                    _mainDispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
                }
                return _current;
            }
        }

        public ObservableCollection<IDetectionEngine> ActiveDetectionEngines
        {
            get
            {
                if (_activeDetectionEngines == null)
                    _activeDetectionEngines = new ObservableCollection<IDetectionEngine>();
                return _activeDetectionEngines;
            }
        }

        public ObservableCollection<string> SelectedDetectionEngines
        {
            get
            {
                if(_selectedDetectionEngines == null)
                    _selectedDetectionEngines = new ObservableCollection<string>();
                return _selectedDetectionEngines;
            }
        }
        

        public bool StripIfModifiedSince
        {
            get { return _stripIfModifiedSince; }
            set
            {
                _stripIfModifiedSince = value;
                NotifyPropertyChanged("StripIfModifiedSince");
            }
        }
        public bool StripPragmaNoCache
        {
            get { return _stripPragmaNoCache; }
            set
            {
                _stripPragmaNoCache = value;
                NotifyPropertyChanged("StripPragmaNoCache");
            }
        }


        #endregion //Properties

        #region Public Functions

        /// <summary>
        /// Starts the fuzzing engine
        /// </summary>
        public void Start(int sessionId)
        {
            Iterations = 0;
            _isRunning = true;
            CurrentFuzzing = ServerFuzzerViewModel.Current;
            foreach (var engine in SelectedDetectionEngines)
                ActiveDetectionEngines.Add(DetectionEngineFactory.Create(engine));

            new Thread(Run).Start(sessionId);
        }

        /// <summary>
        /// Stops the fuzzing engine
        /// </summary>
        public void Stop()
        {

            _isRunning = false;
        }

        /// <summary>
        /// Checks if the fuzzing engine is currently running
        /// </summary>
        public Boolean IsRunning
        {
            get { return _isRunning; }
        }

        #endregion //Public Functions

        #region Private Functions

        private static String GetHeader(string key, ServerFuzzingItem item)
        {
            if (item.Enabled)
            {
                return item.ServerFuzzingEngine.GetNextValue(FuzzerSettingType.String);
            }
            else
            {
                return CurrentFuzzing.HttpHeaders[key];
            }
        }
        private static void AddServerFuzzerResult(TCPWebRequest req, TCPWebResponse resp, int sessionId)
        {
            ResultsManager.FeatureResults.Add(new ServerFuzzerResult()
            {
                CreatedDate = DateTime.Now,
                Description = String.Format("[Session {0}]:Received Non HTTP 200 response for request: {1}",sessionId, resp.StatusCode),
                Feature = HardangerFeature.ServerFuzzer,
                Severity = Severity.Important,
                Request = req,
                Response = resp
            });
            _mainDispatcher.Invoke((System.Action)delegate { ResultCount++; });
            _mainDispatcher.Invoke((System.Action)delegate { Current.CurrentSessionResultCount++; });
        }
        private static void AddResult(TCPWebRequest tcpWebRequest, Exception ex, int sessionId)
        {
            ResultsManager.FeatureResults.Add(new ServerFuzzerResult()
            {
                CreatedDate = DateTime.Now,
                Description = String.Format("[Session {0}]: Error processing request: {1}",sessionId, ex.Message),
                Feature = HardangerFeature.ServerFuzzer,
                Severity = Severity.Informational,
                Request = tcpWebRequest,
            });
            _mainDispatcher.Invoke((System.Action)delegate { ResultCount++; });
            _mainDispatcher.Invoke((System.Action)delegate { Current.CurrentSessionResultCount++; });
        }
        private void Run(object sessionId)
        {

            while (_isRunning)
            {
                int threadsToStart = MaxThreads - CurrentNumberOfThreads;
                if (threadsToStart > 0)
                    SpinThreads(threadsToStart, sessionId);
                Thread.Sleep(1000);
            }
        }
        private void SpinThreads(int numberOfThreads, object sessionId)
        {
            for (int i = 0; i < numberOfThreads; i++)
            {
                if (!_isRunning)
                    return;
                new Thread(SendRequest).Start(sessionId);
            }
        }
        private void SendRequest(object sessionId)
        {
            _mainDispatcher.Invoke((System.Action)delegate { CurrentNumberOfThreads++; });

            TCPWebRequest tcpWebRequest = new TCPWebRequest();
            tcpWebRequest.Uri = BuildQueryString();
            tcpWebRequest.Hostname = CurrentFuzzing.HardangerSession.Hostname;
            tcpWebRequest.HttpVersion = CurrentFuzzing.HardangerSession.HttpVersion;
            tcpWebRequest.Method = CurrentFuzzing.HardangerSession.Method;
            tcpWebRequest.Port = CurrentFuzzing.HardangerSession.Port;
            tcpWebRequest.SSL = CurrentFuzzing.HardangerSession.SSL;
            
            tcpWebRequest.Headers = BuildHeaders();

            if (tcpWebRequest.Method.ToUpper() == "POST")
            {
                tcpWebRequest.Post = BuildBody();
            }
            TCPWebResponse response;
            try
            {
                response = TCPWebClient.Process(tcpWebRequest, RequestTimeOut);
                AnalyzeResponse(tcpWebRequest, response, (int)sessionId);
            }
            catch (Exception ex)
            {
                if (SessionManager.Current.CallingDispatcher.Thread == Thread.CurrentThread)
                {
                    AddResult(tcpWebRequest, ex, (int)sessionId);
                }
                else
                {
                    SessionManager.Current.CallingDispatcher.BeginInvoke((System.Action)delegate
                    {
                        AddResult(tcpWebRequest, ex, (int)sessionId);
                    });
                }
            }
            Thread.Sleep(RequestPause);
            _mainDispatcher.Invoke((System.Action)delegate { Iterations++; });
            _mainDispatcher.Invoke((System.Action)delegate { CurrentNumberOfThreads--; });
        }
        private void AnalyzeResponse(TCPWebRequest req, TCPWebResponse resp, int sessionId)
        {
            /*
            System.IO.StreamReader sr = new System.IO.StreamReader(resp.GetResponseStream());
            return sr.ReadToEnd();
             * 
             */
            foreach (var engine in ActiveDetectionEngines)
            {
                if (engine.ProcessResponse(resp))
                {
                    if (SessionManager.Current.CallingDispatcher.Thread == Thread.CurrentThread)
                    {
                        AddServerFuzzerResult(req, resp, sessionId);
                    }
                    else
                    {
                        SessionManager.Current.CallingDispatcher.BeginInvoke((System.Action)delegate
                        {
                            AddServerFuzzerResult(req, resp, sessionId);
                        });
                    }
                }
            }
        }
        private HttpPost BuildBody()
        {
            StringBuilder sb = new StringBuilder();

            if (CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.PostParameters].ConfigurationItems.Count > 0)
            {
                foreach (var key in CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.PostParameters].ConfigurationItems.Keys)
                {
                    var item = CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.PostParameters].ConfigurationItems[key];
                    if (item.Enabled)
                    {
                        sb.Append(key);
                        sb.Append("=");
                        sb.Append(item.ServerFuzzingEngine.GetNextValue(FuzzerSettingType.String));
                        sb.Append("&");
                    }
                    else
                    {
                        sb.Append(key);
                        if (!String.IsNullOrEmpty(CurrentFuzzing.PostParameters.Parameters[key]))
                        {
                            sb.Append("=");
                            sb.Append(CurrentFuzzing.PostParameters.Parameters[key]);
                        }
                        sb.Append("&");
                    }
                    sb.Remove(sb.Length - 1, 1);
                }
            }
            return new HttpPost()
            {
                Raw = sb.ToString()
            };
        }
        private Uri BuildQueryString()
        {

            StringBuilder sb = new StringBuilder();
            sb.Append(CurrentFuzzing.HardangerSession.Uri);
            if (CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.GetParameters].ConfigurationItems.Count > 0)
            {
                sb.Append("?");
                foreach (var key in CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.GetParameters].ConfigurationItems.Keys)
                {
                    var item = CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.GetParameters].ConfigurationItems[key];
                    if (item.Enabled)
                    {
                        sb.Append(key);
                        sb.Append("=");
                        sb.Append(item.ServerFuzzingEngine.GetNextValue(FuzzerSettingType.String));
                        sb.Append("&");
                    }
                    else
                    {
                        sb.Append(key);
                        if (!String.IsNullOrEmpty(CurrentFuzzing.GetParameters.Parameters[key]))
                        {
                            sb.Append("=");
                            sb.Append(CurrentFuzzing.GetParameters.Parameters[key]);
                        }
                        sb.Append("&");
                    }
                    sb.Remove(sb.Length - 1, 1);
                }
            }
            return new Uri(sb.ToString());
        }
        private Dictionary<String, String> BuildHeaders()
        {
            Dictionary<String, String> headers = new Dictionary<string, string>();
            if (CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.Headers].ConfigurationItems.Count > 0)
            {
                foreach (var key in CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.Headers].ConfigurationItems.Keys)
                {

                    var item = CurrentFuzzing.FuzzerConfiguration[ServerFuzzingType.Headers].ConfigurationItems[key];
                    headers.Add(key, GetHeader(key, item));
                }
            }
            return headers;
        }
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
        
        #endregion //Private Functions
    }
}
